#
# LsL/Util.pm
#
# Copyright (C) 2004 Albacore Technology, LLC All Rights Reserved
#
#

package LsL::Util;

use strict;
use vars      qw
          (
            $VERSION
            
            @ISA @EXPORT @EXPORT_OK
            
            $MIN_DATE
            $NULL_LIST
            %YES_NO
          );
use CGI::Util    qw(rearrange);
use Exporter;
use Date::Calc    qw(:all);
use Digest::MD5; 

$VERSION = 0.02;
@ISA     = qw(Exporter);
@EXPORT  = qw();
@EXPORT_OK =    qw();

$MIN_DATE =      "2000-01-01";
$NULL_LIST =    [ undef ];
%YES_NO = ( y => 'Yes', n => 'No', Y => 'Yes', N => 'No' );

# utilities

sub commify {
  local $_  = shift;
  1 while s/^([-+]?\d+)(\d{3})/$1,$2/;
  return $_;
}

sub my_money {
  my ($a,$dash) = @_;

  $a = 0 if !$a;

  return '-' if !$a && $dash;

  $a = sprintf("%0.2f", $a);

  my $b = commify($a);

  $a < 0 && do {
    $b =~ s/-//g;
    return '$(' . $b .')';
  };

  return '$' . $b;
}

sub my_ix {
  my ($num) = @_;

  for (substr($num,-1)) {
    /^1$/ && do { $num = $num . 'st'; last; };
    /^2$/ && do { $num = $num . 'nd'; last; };
    /^3$/ && do { $num = $num . 'rd'; last; };
    do { $num = $num . 'th'; last; };
  }

  return $num;
}

sub my_money_plain {
  my ($a,$dash) = @_;

  $a = 0 if !$a;

  return '-' if !$a && $dash;

  $a = sprintf("%0.2f", $a);

  my $b = commify($a);

  return $b;
}

sub is_arrayref
{
  return 0 unless( @_ && defined $_[ 0 ] && ref $_[ 0 ] eq 'ARRAY' );
  return 1;
}


sub is_hashref
{
  return 0 unless( @_ && defined $_[ 0 ] && ref $_[ 0 ] eq 'HASH' );
  return 1;
}


sub make_arrayref
{
  return $_[ 0 ] if( is_arrayref( @_ ) );
  return [ @_ ];
}


sub slice
{
  my $in = shift;
  my %out;
  $out{ $_ } = $in->{ $_ } for (@_);
  return \%out;
}

sub format_phone
{
  # returns (AAA) EEE-NNNN
  my ($phone) = args( ['phone'], @_ );
  return undef unless( $phone and $phone =~ m|^\d+$| );
  $phone =~ m|([0-9]{3})([0-9]{3})([0-9]{4})([0-9]*)|;
  return qq{($1) $2-$3 x$4} if( defined $4 && length( $4 ) > 0 );
  return qq{($1) $2-$3};
}

sub format_date
{
  # takes a date YYYY-MM-DD and returns Month DD, YYYY
  my $date = shift;
  return month_string( $2 ) . " " . int( $3 ) . ", " . $1
    if( $date and $date =~ m|^(\d{4})-(\d{1,2})-(\d{1,2})$| );
  return undef;
}


sub format_short_date
{
  # takes a date YYYY-MM-DD and returns MON DD, YYYY
  my $date = shift;
  return substr( month_string($2), 0, 3 ) . " " . int($3) . ", " . $1
    if($date and $date =~ m|^(\d{4})-(\d{1,2})-(\d{1,2})$| );
  return undef;
}


sub format_numeric_date
{
  # takes a date YYYY-MM-DD and returns MM/DD/YYYY
  my $date = shift;
  return sprintf( "%02d/%02d/%04d", $2, $3, $1 )
    if($date and $date =~ m|^(\d{4})-(\d{1,2})-(\d{1,2})$| );
  return undef;
}


sub format_hh_mm
{
  my $hh_mm = shift;
  my ($h, $m) = split( ":", $hh_mm );
  return undef unless( $h && $m );
  return int($h) . ":" . $m;
}


sub month_string
{
  # takes a number and returns the text month
  my $n = shift;
  return undef unless($n and $n =~ m|^\d+$|);
  $n = int($n);
  my @months = qw(January February March April May June July August September October November December);
  return $months[$n - 1];
}


sub display_date
{
  # returns MON DD, YYYY
  my $t = shift;
  $t = time() unless( defined $t );
  return format_date( iso_date($t) );
}


sub month_day_suffix
{
  # does the fancy 1st 2nd 3rd suffix
  my $day = shift;
  my $last_digit = substr( $day, (length($day) - 1), 1 );  
  my %decode = qw(0 th 1 st 2 nd 3 rd);
  
  return qq{$day$decode{ $last_digit }}
    if( exists $decode{ $last_digit } );
    
  return qq{${day}th};
}


sub iso_date
{
  # returns ISO-8601 YYYY-MM-DD date string:
  # http://serendipity.magnet.ch/hermetic/cal_stud/formats.htm
  my $t = shift;
  $t = time() unless( defined $t );
  my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime( $t );
  return sprintf( "%04d-%02d-%02d", $year + 1900, $mon + 1, $mday );
}


sub split_iso_date
{
  # returns an array (year, month, day) from an ISO-8601 date string 
  my $date = shift;
  return (undef, undef, undef) unless( defined $date );
  $date =~ m|^(\d{4})-(\d+)-(\d+)$|;
  return ($1, $2, $3);
}


sub concat_iso_date
{
  # returns a concatenated, formatted ISO-8601 date string
  my ($y, $m, $d) =
    args( [['y', 'yyyy', 'year'], ['m', 'mm', 'month'], ['d', 'dd', 'day']], @_ );
  return iso_date unless( defined $y && defined $m && defined $d );
  $y =~ s|\D||g;
  $m =~ s|\D||g;
  $d =~ s|\D||g;
  return undef unless( $y && $m && $d );
  return sprintf( "%04d-%02d-%02d", $y, $m, $d );
}


sub valid_iso_date
{
  # returns true if argument is a valid ISO-8601 date string
  my $date = shift;
  my ($y, $m, $d) = split_iso_date( $date );
  return 0 unless( defined( $y ) && defined( $m ) && defined( $d ) );
  return 0 unless( $m >= 1 && $m <= 12 );
  return 0 unless( $d >= 1 && $d <= Days_in_Month( $y, $m ) );
  return 1;
}


sub range_check_iso_date
{
  # returns true if date is between min and max, expressed as ISO-8601 date strings
  my ($date, $min, $max) = args( ['date', 'min', 'max'], @_ );
  $date = iso_date() unless( $date );
  $min = $MIN_DATE unless( $min );
  $max = iso_date() unless( $max );
  return 0 unless( valid_iso_date( $date ) && valid_iso_date( $min ) && valid_iso_date( $max ) );
  return 0 unless( $date gt $min || $date eq $min );
  return 0 unless( $date lt $max || $date eq $max );
  return 1;
}


sub min_to_hour
{
  # returns formatted hr min string given a number of minutes
  my $total_minutes = shift;
  return undef unless( $total_minutes );
  
  my $hours = int( $total_minutes / 60 );
  my $minutes = $total_minutes - ( $hours * 60 );
  
  my $s = ($hours > 1 ? "s" : "");
  my $hours_text = ($hours ? "$hours hr$s" : "");
  my $minutes_text = ($minutes eq 0 ? "" : "$minutes min");
  
  my $space = ($hours_text and $minutes_text ? " " : "");
  
  return qq{${hours_text}${space}${minutes_text}};
}


sub military_to_regular_time
{
  # returns hh:mm [AM/PM] given a military time
  my $military_time = shift || '';
  my ($hh, $mm) = split( ":", $military_time );
  
  return '' unless( $hh && $mm );

  $hh = int( $hh );
  $mm = int( $mm );

  my $ampm = 'PM';
  $ampm = 'AM' if( $hh < 12 );

  $hh -= 12 if( $hh > 12 );
  $hh += 12 if( $hh == 0 );

  $mm = "0$mm" if( $mm < 10 );

  return wantarray
    ? ( $hh, $mm, $ampm )
    : "$hh:$mm $ampm";
}


sub regular_to_military_time
{
  my ($hh, $mm, $ampm) = args( ['hh', 'mm', 'ampm'], @_ );

  $hh = sprintf("%02d", $hh) if $hh;
  $mm = sprintf("%02d", $mm) if $mm;

  $hh += 12 if( ($ampm eq 'PM') && ($hh != 12) );
  $hh = "00" if( ($hh == 12) && ($ampm eq "AM") );
  return "$hh:$mm";
}


sub subtract_military_times
{
  my ($time_in, $time_out) = args( ['time_in', 'time_out'], @_ );
  return undef unless( $time_in && $time_out );
  
  my ($ti_hh, $ti_mm) = split( ":", $time_in );
  my ($to_hh, $to_mm) = split( ":", $time_out );

  $ti_hh = int( $ti_hh );
  $ti_mm = int( $ti_mm );
  $to_hh = int( $to_hh );
  $to_mm = int( $to_mm );

  return ( (($to_hh * 60) + $to_mm) - (($ti_hh * 60) + $ti_mm) );
}


sub format_float
{
  # returns %.2f for whatever float is passed
  my $float = shift;
  my $format_float = 0;
  return $format_float if( $format_float = sprintf( "%.2f", $float ) );
  return 0;
}


sub null
{
  return $NULL_LIST;
}


sub is_null
{
  return 0 unless( @_ && defined $_[ 0 ] );
  return 1 if( $_[ 0 ] eq $NULL_LIST ||
    (ref $_[ 0 ] eq 'ARRAY' && @{$_[ 0 ]} == 1 && !defined $_[ 0 ]->[ 0 ]) );
  return 0;
}

sub check_phone
{
  my ($name, $hash) = @_;

  # only check if we actually got something
  return 1
    unless( $hash->{ "${name}_a" } || $hash->{ "${name}_e" } || $hash->{ "${name}_n" } || $hash->{ "${name}_x" } );

  if( $hash->{ "${name}_a" } && $hash->{ "${name}_e" } && $hash->{ "${name}_n"
} )
  {
    my $ext = (exists $hash->{ "${name}_x" } && defined $hash->{ "${name}_x" }) ? $hash->{ "${name}_x" } : '';
    my $phone = qq{$hash->{ "${name}_a" }$hash->{ "${name}_e" }$hash->{ "${name}_n" }$ext};

    # check the format
    return undef
      if( $hash->{ "${name}_a" } !~ m|^[0-9]{3}$| ||
        $hash->{ "${name}_e" } !~ m|^[0-9]{3}$| ||
        $hash->{ "${name}_n" } !~ m|^[0-9]{4}$| );
    return undef if( $ext && $ext =~ m|[^0-9]| );

    delete $hash->{ "${name}_a" };
    delete $hash->{ "${name}_e" };
    delete $hash->{ "${name}_n" };
    delete $hash->{ "${name}_x" };

    $hash->{ $name } = $phone;

    return $phone;
  }

  return undef;
}

sub check_date {
  my ($name, $hash, $min, $max) = @_;

  # get fields
  my $year  = $hash->{ "${name}y" };
  my $month = $hash->{ "${name}m" };
  my $day   = $hash->{ "${name}d" };

  # delete them from the hash (do not use them after calling check_date!)
  delete $hash->{ "${name}d" };
  delete $hash->{ "${name}m" };
  delete $hash->{ "${name}y" };

  # remove non-digits, ie MM DD YYYY
  $day   =~ s/[^0-9]//g;
  $year  =~ s/[^0-9]//g;
  $month =~ s/[^0-9]//g;

  # only check if we actually got something
  return 1 unless $day || $month || $year;

  if ($day && $month && $year) {
    my $iso_date = LsL::Util::concat_iso_date( $year, $month, $day );
    return undef unless LsL::Util::range_check_iso_date(-date => $iso_date,
      -min => $min, -max => $max);

    $hash->{ $name } = $iso_date;
    return undef unless( $hash->{ $name } );
    return $iso_date;
  }

  return undef;
}

sub limit_query
{
  my ($sql, $start, $count) = @_;

  return $sql unless( defined $start && defined $count );
  $start =~ tr|0-9||dc;
  $count =~ tr|0-9||dc;
  return $sql unless( length $start && length $count );

  # build start/end
  my $sql_start = $start + 1;
  my $sql_end = $start + $count + 1;

  return qq
  {
    select * from
    (
      select
        rownum as row_number,
        QUERY.*
      from
        ($sql) QUERY
    ) where row_number between $sql_start and $sql_end
  };
}

sub current_time {
  my ($string) = @_;

  my $tz = $ENV{ 'TIMEZONE' } || 'PST';

  my $time = HTML::Shark::D_Util::current_time({'tz' => $tz});

  my ($hh, $mm, $am) = split( ":", $time );

  # returns $hh:$mm $am
  return int($hh) . ":$mm $am" if $string;

  # returns $hh, $mm, $am
  return int($hh), $mm, $am;
}
1;
